<?php
/**
 * CONTENIDO extension pack (mpContenidoX), consists of multiple classes to
 * provide more features for Plugins/Modules.
 *
 * @package     mpContenidoX
 * @subpackage  PluginInstaller
 * @author      Murat Purc <murat@purc.de>
 * @copyright   Copyright (c) 2008-2012 Murat Purc (http://www.purc.de)
 * @license     http://www.gnu.org/licenses/gpl-2.0.html - GNU General Public License, version 2
 * @version     $Id: Sql.class.php 21 2012-07-16 21:33:14Z purcaholic $
 */


defined('CON_FRAMEWORK') or die('Illegal call');


/**
 * Plugin plugin installer application class. Initializes the installer.
 *
 * @package     mpContenidoX
 * @subpackage  PluginInstaller
 * @author      Murat Purc <murat@purc.de>
 * @copyright     2009 Murat Purc
 */
class mpPluginInstaller_Sql implements Iterator, Countable
{
    private $_cfg;

    private $_rawSql;

    private $_delemiter;

    private $_cleanedSql;

    private $_sqlPieces;

    private $_pos;


    public function __construct($sql, $delemiter=';')
    {
        $this->_cfg        = $GLOBALS['cfg'];
        $this->_delemiter  = $delemiter;
        $this->_rawSql     = $sql;
        $this->_cleanedSql = $this->_removeRemarks($sql);
        $this->_sqlPieces  = $this->_splitSql($sql);
    }

    public function getDelemiter()
    {
        return $this->_delemiter;
    }

    public function getRawSql()
    {
        return $this->_rawSql;
    }

    public function getCleanedSql()
    {
        return $this->_cleanedSql;
    }

    public function implodeToSql()
    {
        return implode($this->_delemiter, $this->_sqlPieces);
    }

    public function rewind()
    {
        $this->_pos = 0;
    }

    public function valid()
    {
        return $this->_pos < count($this->_sqlPieces);
    }

    public function key()
    {
        return $this->_pos;
    }

    public function current()
    {
        return $this->_sqlPieces[$this->_pos];
    }

    public function next()
    {
        $this->_pos++;
    }

    /**
     * Returns number of sql statements (Countable interface implementation).
     *
     * @return  int
     */
    public function count()
    {
        return count($this->_sqlPieces);
    }


    public function doPidReplacements($id)
    {
        // generate !PID! replacement
        $PID     = 100 + $newId;
        $replace = array('!PREFIX!' => $this->_cfg['sql']['sqlprefix'], '!PID!' => $PID);
        $this->rewind();
        foreach ($this->_sqlPieces as $p => $v) {
            $this->_sqlPieces[$p] = strtr($v, $replace);
        }
        $this->rewind();
    }


    /**
     * Removes '# blabla...' from the mysql_dump.
     * This function was originally developed for phpbb 2.01
     * (C) 2001 The phpBB Group http://www.phpbb.com
     *
     * @param   string  The SQL statements
     * @return  string  SQL statements without the remarks
     */
    protected function _removeRemarks($sql) {
        $lines = explode("\n", $sql);

        // try to keep mem. use down
        $sql = '';

        $linecount = count($lines);
        $newSql = '';

        for ($i = 0; $i < $linecount; $i++) {
            if (($i != ($linecount - 1)) || (strlen($lines[$i]) > 0)) {
                $newSql .= ($lines[$i][0] != '#')  ? $lines[$i]."\n" : "\n";
                // Trading a bit of speed for lower mem. use here.
                $lines[$i] = '';
            }
        }
        return $newSql;
    }


    /**
     * Splits sql- statements into handy pieces.
     * This function was original developed for the phpbb 2.01
     * (C) 2001 The phpBB Group http://www.phpbb.com
     *
     * @param   string  The SQL statement
     * @return  array
     */
    function _splitSql($sql, $delimiter=';')
    {
        // split up our string into "possible" sql statements.
        $tokens = explode($this->_delemiter, $sql);

        // try to save mem.
        $sql = '';
        $statements = array();

        // we don't actually care about the matches preg gives us.
        $matches = array();

        // this is faster than calling count($oktens) every time thru the loop.
        $tokenCount = count($tokens);
        for ($i = 0; $i < $tokenCount; $i++) {
            // dont wanna add an empty string as the last thing in the array.
            if (($i != ($tokenCount - 1)) || (strlen($tokens[$i] > 0))) {
                // this is the total number of single quotes in the token.
                $totalQuotes = preg_match_all("/'/", $tokens[$i], $matches);
                // counts single quotes that are preceded by an odd number of backslashes,
                // which means they're escaped quotes.
                $escapedQuotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$i], $matches);

                $unescapedQuotes = $totalQuotes - $escapedQuotes;
                // if the number of unescaped quotes is even, then the delimiter did not occur inside a string literal.
                if (($unescapedQuotes % 2) == 0) {
                    // it's a complete sql statement.
                    $statements[] = $tokens[$i];
                    // save memory.
                    $tokens[$i] = '';
                } else {
                    // incomplete sql statement. keep adding tokens until we have a complete one.
                    // $temp will hold what we have so far.
                    $temp = $tokens[$i] . $delimiter;
                    // save memory..
                    $tokens[$i] = '';
                    // do we have a complete statement yet?
                    $completeStmt = false;

                    for ($j = $i + 1; (!$completeStmt && ($j < $tokenCount)); $j++) {
                        // this is the total number of single quotes in the token.
                        $totalQuotes = preg_match_all("/'/", $tokens[$j], $matches);
                        // counts single quotes that are preceded by an odd number of backslashes,
                        // which means theyre escaped quotes.
                        $escapedQuotes = preg_match_all("/(?<!\\\\)(\\\\\\\\)*\\\\'/", $tokens[$j], $matches);

                        $unescapedQuotes = $totalQuotes - $escapedQuotes;

                        if (($unescapedQuotes % 2) == 1) {
                            // odd number of unescaped quotes. In combination with the previous incomplete
                            // statement(s), we now have a complete statement. (2 odds always make an even)
                            $statements[] = $temp . $tokens[$j];
                            // save memory.
                            $tokens[$j] = '';
                            $temp = '';
                            // exit the loop.
                            $completeStmt = true;
                            // make sure the outer loop continues at the right point.
                            $i = $j;
                        } else {
                            // even number of unescaped quotes. We still dont have a complete statement.
                            // (1 odd and 1 even always make an odd)
                            $temp .= $tokens[$j] . $delimiter;
                            // save memory.
                            $tokens[$j] = '';
                        }
                    }
                }
            }
        }
        return $statements;
    }

}
